<!--
20240814最后修改
@author ljy
对话框公共组件，用于统一对话框的样式。本主键不对对话框的主体部分进行处理
一、使用方法
1.引入组件
import BaseDialog from '@/components/base/dialog/BaseDialog.vue';
2.调用组件
<BaseDialog ref="baseDialogRef" :dialogWidth="title" @closed="closed">
3.定义该组件对象
//定义对话框组件对象
const baseDialogRef = ref();
4.打开和关闭对话框的方法
baseDialogRef.value.openDialog();
baseDialogRef.value.closeDialog();
5.对话框默认提交事件回调
说明：表单提交事件（组件不进行任何提交的实际处理，需要父组件完成）
 （1）基本提交
首先在标签中加入事件 @dialogSubmit="dialogSubmit()"
<BaseDialog ref="baseDialogRef" v-bind="$props" @dialogSubmit="dialogSubmit()">
然后在setup中调用即可
 const dialogSubmit = () => {
  //表单提交代码
}
（1）对话框高级提交事件-例如对话框中包含表单，那就要将表单对象传入
首先在标签中加入事件@dialogSubmit="dialogSubmit(formRef)"
<BaseDialog ref="baseDialogRef" v-bind="$props" @dialogSubmit="dialogSubmit(formRef)">
然后在setup中调用即可
const formRef = ref<FormInstance>(); // 定义表单的ref对象
//执行回调函数
const dialogSubmit = async (formEl: FormInstance | undefined) => {
  console.log(111);
  // 提交表单
  if (!formEl) return;
  // 表单验证
  await formEl.validate(async (valid, fields) => {
    // 如果通过表单验证
    if (valid) {
    }
  });
};
6.对话框关闭事件回调
说明：表单提交事件（组件不进行任何提交的实际处理，需要父组件完成）
 （1）基本提交
 首先在标签中加入事件 @dialogClosed="closed"
  <BaseDialog ref="baseDialogRef" v-bind="$props" :dialogTitle="titleResult" @closed="closed" @dialogSubmit="dialogSubmit(formRef)">
 然后在setup中调用
const closed = () => {
  // 重置表单
  formRef.value!.resetFields();
  // Object.assign(formObject.value, formInitObject);
  // 关闭对话框后还要处理的信息继续传递父组件
  emit('afterCloseDialog');
};
  -->
<template>
  <div class="dialog">
    <!-- 如果要先全属性props传递，再给特殊的值做传递，要按照下面这个代码顺序（v-bind="$props" :dialogTitle="titleResult"）传递 -->
    <BaseDialog ref="baseDialogRef" v-bind="$props" :dialogTitle="titleResult" @dialogClosed="closed" @dialogSubmit="dialogSubmit(formRef)">
      <template #dialog_body>
        <el-form
          ref="formRef"
          :inline="formInline"
          :model="formObject"
          :rules="formRules"
          :label-width="formLabelWidth"
          :label-position="formLabelPosition"
        >
          <template v-for="(item, index) in formHeader" :key="index">
            <!--如果有分割线，则显示分页线-->
            <el-divider v-if="item.type === 'divider'" class="foot_divider" />
            <el-form-item
              v-else
              :label="item.label + ':'"
              :prop="item.prop"
              :rules="item.disabled == true ? [] : item.rules"
              v-show="item.show != false"
            >
              <!--如果是自定义插槽类型-->
              <span v-if="item.type === 'custom'">
                <slot :name="item.prop" v-bind:item="item" v-bind:formItem="formObject[item.prop]"> </slot>
              </span>
              <!--如果是文本域类型-->
              <el-input
                v-else-if="item.type === 'textarea'"
                type="textarea"
                :placeholder="`${item.placeholder ? item.placeholder : item.label}`"
                :disabled="item.disabled"
                :autosize="{ minRows: 4, maxRows: 6 }"
                v-model="formObject[item.prop]"
                clearable
                :style="`width:${item.width ? item.width : ''}`"
              />
              <!--
             如果是日期类型
              新增：value-format属性，日期选择后的返回类型 x：时间戳类型
              修改时间：2023-01-16
              修改人：qzy
            -->
              <el-date-picker
                v-else-if="item.type === 'date'"
                type="date"
                v-model="formObject[item.prop]"
                :placeholder="`${item.placeholder ? item.placeholder : item.label}`"
                :disabled="item.disabled"
                :size="dataPickerSize"
                :value-format="item.dateType"
                :style="`width:${item.width ? item.width : ''}`"
              />
              <!--如果是swich类型-->
              <el-switch
                v-else-if="item.type === 'switch'"
                v-model="formObject[item.prop]"
                inline-prompt
                active-text="是"
                inactive-text="否"
                :readonly="item.readonly"
                :disabled="item.disabled"
                :style="`width:${item.width ? item.width : ''}`"
              />
              <!--如果是普通选择框。针对element plus2.5下拉框无法自设宽度的问题，这里增加了宽度的设计-->
              <el-select
                v-else-if="item.type === 'select'"
                v-model="formObject[item.prop]"
                clearable
                :placeholder="`${item.placeholder ? item.placeholder : item.label}`"
                :style="`width:${item.width ? item.width : '200px'}`"
              >
                <el-option v-for="(item1, index1) in item.dict" :key="index1" :label="item1.label" :value="item1.value"></el-option>
              </el-select>
              <el-input
                v-else-if="item.type === 'number'"
                v-model.number="formObject[item.prop]"
                :readonly="item.readonly"
                :disabled="item.disabled"
                :placeholder="`${item.placeholder ? item.placeholder : item.label}`"
                clearable
                :style="`width:${item.width ? item.width : ''}`"
              />
              <el-input-number
                v-else-if="item.type === 'integer'"
                v-model="formObject[item.prop]"
                :precision="0"
                :placeholder="`${item.placeholder ? item.placeholder : item.label}`"
                :readonly="item.readonly"
                :disabled="item.disabled"
                controls-position="right"
                clearable
                :style="`width:${item.width ? item.width : ''}`"
              />
              <!--如果是图标选择类型，element plus的图标，定制开发。注意选择框宽度可以自己调整-->
              <IconSelect
                v-else-if="item.type === 'iconSelect'"
                v-model="formObject[item.prop]"
                :placeholder="`${item.placeholder ? item.placeholder : item.label}`"
                :readonly="item.readonly"
                :disabled="item.disabled"
                :selectWidth="item.width"
              />
              <!--如果没有type类型，默认是input类型-->
              <el-input
                v-else
                v-model="formObject[item.prop]"
                :type="`${item.type && item.type === 'password' ? 'password' : 'text'}`"
                :readonly="item.readonly"
                :disabled="item.disabled"
                :placeholder="`${item.placeholder ? item.placeholder : item.label}`"
                clearable
                :style="`width:${item.width ? item.width : ''}`"
              />
            </el-form-item>
          </template>
        </el-form>
      </template>
    </BaseDialog>
  </div>
</template>

<script lang="ts" setup>
/**
 * 特别说明
 * 1.本组件不开发表单的重置功能
 * （1）设计思路：由于本组件有特殊表单元素的插槽，因此表单对象是从父组件传入，按照vue的设计思维，重置应该在父组件完成
 * （2）技术上无法实现完美的表单重置功能。
 * 因为对话框组件重置时记录的是第一次对话框打开时的初始值，因此在先执行编辑再执行添加会出现问题。现象是添加成功并重置表单后，表单数据变为第一次打开时的数据。
 * 例如第一次打开是账户user的编辑信息，那么无论后续操作了多少次对话框，执行formRef.value!.resetFields();清空表单后，都会重置为账户user的编辑信息。
 * 因为在props无法修改form的信息，因此建议在父组件去操作手动清空数据。
 * 2.功能扩展
 * （1）因为element plus从2.5版本后下拉框的宽度均要自己设置，为了保证下拉框布局问题，特意针对每个类似下拉框的组件增加宽度属性，但这个属性将不对其他文本框生效（因为其他文本框可以自动获取宽度）
 *
 */
import BaseDialog from '@/components/base/dialog/BaseDialogComponent.vue';

import { ref, computed, inject, reactive } from 'vue';
import type { FormHeader, ResponseData } from '@/types/interface/base_interface';
import { ElMessage } from 'element-plus';
import type { FormInstance } from 'element-plus';
import IconSelect from '@/components/base/form/IconSelect.vue';
import { defineEmits } from 'vue';
const emit = defineEmits(['afterCloseDialog', 'beforeSubmit', 'afterSubmit', 'afterSubmitSuccess']);

// 接收父组件的值
interface Props {
  // form属性在20220430修改为可选属性，意图避免在父组件初始化时，避免DIALOG初始化该属性报UNDEFINED
  // 虽然在父组件使用诸如const form = ref<Department>({});可以解决此问题，但是在typescript的Department接口中，如果包含了必须的属性，则该表单必须加上必须属性的值
  // 例如：const form = ref<Department>({ id: 1, name: '大数据技术'})，导致父组件应用中缺乏灵活性
  form?: Record<string, any>; // 表单对象。
  formHeader: FormHeader[]; // 表单元素集合,用于生成表单
  formInline?: boolean; // 表单是否未行内元素，对应form的inline属性。默认不是
  action?: string; // 表单的处理类型，主要有post、put等，分别代表插入和编辑。
  formRules?: any; // 整个表单的校检规则
  formLabelWidth?: string; // 表单宽度，对应el-form的label-width属性
  formLabelPosition?: string; // 表单的标签对齐方式，对应el-form的label-position属性
  url?: string; // 表单添加或编辑的地址，要求在服务器的接口地址一样，只是方式不同。
  dataPickerSize?: 'default' | 'large' | 'small'; //日期输入框尺寸
  //下面是基础对话框组件的基础属性（直接复制）
  dialogWidth?: string; // 对话框宽度
  dialogTitle?: string; // 对话框标题,前台传入，不用加入添加或编辑的文本。添加或编辑的文本由action属性决定
  showDialogCancelButton?: Boolean; // 是否显示取消按钮
  dialogConfirmButtonText?: string; // 确认按钮上的文字
  showDialogFoot?: Boolean; // 是否显示对话框脚部
  dialogBodyStyle?: any; // 对话框主体内容的样式，要求以对象方式传入
}
import Request from '@/api/request/request';

const props = withDefaults(defineProps<Props>(), {
  form: () => ({}), // 返回空对象
  formInline: false,
  action: 'post',
  formLabelWidth: '100px',
  formLabelPosition: 'right',
  rules: reactive({})
});
// inject{
//   loadTableList:{value:loadTableList,default:null}
// }
// 接收父容器传递过来的表格刷新函数，用于刷新表格信息
//注意，使用这种方式控制台不会警告，但是下面注释掉的方式会警告
//如果传递的是参数可以用下面写法
// const loadTableList = inject('loadTableList', null);
//如果传递的是函数可以用下面写法
const loadTableList = inject<() => void>('loadTableList', () => {});
//注意，使用这种方式控制台不会警告，但是下面注释掉的方式会警告
// if (typeof inject<() => void>('loadTableList') !== 'undefined') {
//   loadTableList = inject<() => void>('loadTableList'); // 表格函数
// }

// const formInitObject = computed(JSON.parse(JSON.stringify(unref(props.form)))); // 用于表单恢复初始值
// console.log(props.form);
// if (props.form) {
//   console.log('form初始化');
//   console.log(props.form);
//   const formInitObject = computed(JSON.parse(JSON.stringify(unref(props.form)))); // 用于表单恢复初始值
//   Object.keys(props.form).forEach((key) => {
//     console.log(key);
//   });
// }
//定义对话框组件对象
const baseDialogRef = ref();
// 表单对象
const formObject = computed(() => props.form); // 表单的实体类
// 表单初始值
// const formInitObject = computed(JSON.parse(JSON.stringify(unref(formObject)))); // 用于表单恢复初始值
// 对话框标题，根据action生成标题和相应的操作提示
const titleResult = computed(() => {
  let title;
  if (props.action === 'post') {
    title = `${props.dialogTitle}添加`;
  } else if (props.action === 'put' || props.action === 'patch') {
    title = `${props.dialogTitle}编辑`;
  } else {
    title = props.dialogTitle;
  }
  return title;
});
const formRef = ref<FormInstance>(); // 定义表单的ref对象
// 打开此对话框的函数
const openDialog = () => {
  // 第一次打开对话框时会因为form没渲染导致formRef无法获取到dom对象，因此先进行判断。如果是第一次打开时则不进行表单重置，也没必要
  if (formRef.value !== undefined) {
    // 重点也是把上一次的表单验证状态去掉
    formRef.value!.resetFields();
  }
  baseDialogRef.value.openDialog();
};
const closeDialog = () => {
  baseDialogRef.value.closeDialog();
};
// 关闭对话框时候
const closed = () => {
  // 重置表单
  formRef.value!.resetFields();
  // Object.assign(formObject.value, formInitObject);
  // 关闭对话框后还要处理的信息交给父组件
  emit('afterCloseDialog');
};
// 移除不可用的表单元素属性,在表单元素为动态显示的时候，适合清除多余的空属性
const clearObjectDisabledParam = () => {
  props.formHeader.forEach((item: FormHeader) => {
    // 遍历表单元素数组
    if (item.disabled === true) {
      // 如果某个表单元素之不可用
      delete formObject.value[item.prop]; // 移除该属性
    }
  });
};
// 提交表单函数,包含添加和编辑操作
// 如果表单为disabled则不提交
const dialogSubmit = async (formEl: FormInstance | undefined) => {
  console.log(111);
  // 提交表单
  if (!formEl) return;
  // 表单验证
  await formEl.validate(async (valid, fields) => {
    // 如果通过表单验证
    if (valid) {
      // console.log(props.form);
      // Object.keys(props.form).forEach((key) => {
      //   console.log(key);
      // });
      if (props.url) {
        // 如果传入表单地址
        let resData: ResponseData;
        // 移除不可用的表单元素，避免提交多余数据
        clearObjectDisabledParam();
        // 提交表单前要处理的钩子函数，如果要在提交服务端前处理一些数据，可以在这里处理，例如处理关联对象的值，将departmentId处理为department.id再传递
        emit('beforeSubmit', formObject.value);
        if (props.action === 'post') {
          resData = await new Request(props.url, JSON.stringify(formObject.value)).post();
        } else if (props.action === 'put') {
          resData = await new Request(props.url, JSON.stringify(formObject.value)).put();
        } else if (props.action === 'patch') {
          resData = await new Request(props.url, JSON.stringify(formObject.value)).patch();
        } else {
          //作为扩展使用，暂时在组建中没用到此处，只是为了不报错。
          resData = await new Request(props.url, JSON.stringify(formObject.value)).delete();
        }
        // 编写要在提交后操作的代码
        emit('afterSubmit', resData);
        if (resData.success) {
          // 清空表单值
          // formRef.value!.resetFields();
          // 钩子函数，提交表单成功后的操作
          // 如果要清空值建议在这里清空
          emit('afterSubmitSuccess');
          // 将表格重新刷新
          if (loadTableList != null) {
            loadTableList();
          }
          if (props.action === 'put') {
            // 只有修改成功，才会关闭对话框，添加操作则保持对话框状态，以便继续添加。
            baseDialogRef.value.closeDialog();
          }
          ElMessage({
            message: `${titleResult.value}成功`,
            type: 'success'
          });
        }
      }
    }
  });
};

defineExpose({
  // 参考vue3官方文档，需要将函数暴露出去才能被父组件调用，setup模式是不暴露的
  openDialog,
  closeDialog
});
</script>

<style lang="scss" scoped>
.dialog {
  // :veep(.el-select) {
  //   // 重写element plus选择框的宽度，因此此处选择框的宽度无法正常显示.
  //   //原因如下：在版本 2.5.0之后， el-select 的默认宽度更改为 100% 当使用内联形式时，宽度将显示异常。 为了保持显示正常, 您需要手动配置 el-select 的宽度 (如: 例子).
  //   width: 200px;
  // }
  :deep(.el-dialog__header) {
    /*防止父容器设置了统一的右对齐，导致对话框默认的左对齐变为右对齐。*/
    text-align: left;
  }

  :deep(.el-dialog__body) {
    /*修改el-dialog对话框的body样式*/
    padding: 10px 15px; /*element中dialog组件的body默认样式是padding：30px 20px;*/
  }

  .title_divider {
    margin: 20px 0 5px 0;
  }

  .foot_divider {
    margin: 0 0 20px 0;
  }
}
</style>
